package com.atguigu.exam.service.impl;


import com.atguigu.exam.entity.*;
import com.atguigu.exam.mapper.*;
import com.atguigu.exam.service.ExamRecordService;
import com.atguigu.exam.service.PaperQuestionService;
import com.atguigu.exam.service.PaperService;
import com.atguigu.exam.vo.AiPaperVo;
import com.atguigu.exam.vo.PaperVo;
import com.atguigu.exam.vo.RuleVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


/**
 * 试卷服务实现类
 */
@Slf4j
@Service
public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements PaperService {

    @Autowired
    private ExamRecordMapper examRecordMapper;

    @Autowired
    private QuestionMapper questionMapper;

    @Autowired
    private PaperQuestionService paperQuestionService;

    /**
     * 获取试卷详情（包含题目）方法一
     * @param id
     * @return
     */
    @Override
    public Paper customPaperDetailById(Long id) {
        //1. 单表java代码进行paper查询
        Paper paper = getById(id);
        //2. 校验paper == null -> 抛异常
        if (paper == null) {
            throw new RuntimeException("指定id:%s试卷已经被删除，无法查看详情！".formatted(id));
        }
        //3. 根据paperid查询题目集合（中间，题目，答案，选项）
        List<Question> questionList = questionMapper.customQueryQuestionListByPaperId(id);
        //4. 校验题目集合 == null -> 赋空集合！ log->做好记录
        if (ObjectUtils.isEmpty(questionList)) {
            paper.setQuestions(new ArrayList<Question>());
            log.warn("试卷中没有题目！可以进行试卷编辑！但是不能用于考试！！,对应试卷id：{}", id);
            return paper;
        }
        log.debug("题目信息排序前：{}", questionList);
        //对题目进行排序（选择 -> 判断 -> 简答）
        questionList.sort((o1, o2) -> Integer.compare(typeToInt(o1.getType()), typeToInt(o2.getType())));
        //注意：type排序，是字符类型 -》 字符 -》 对应 -》 固定的数字 1 2 3
        log.debug("题目信息排序后：{}", questionList);
        //进行paper题目集合赋值
        paper.setQuestions(questionList);
        return paper;
    }

    /**
     * 手动创建试卷
     * @param paperVo
     * @return
     */
    @Override
    public Paper makePaperBySelf(PaperVo paperVo) {

        //1、完善试卷信息
        //将VO的数据赋值到一个新的Paper对象中
        Paper paper = new Paper(); // 实例化Paper对象
        paper.setName(paperVo.getName()); // 设置名称
        paper.setDescription(paperVo.getDescription()); // 设置描述
        paper.setDuration(paperVo.getDuration()); // 设置时长
        paper.setStatus("DRAFT"); // 新建试卷默认为草稿状态

        //判断是否有题目
        if (ObjectUtils.isEmpty(paperVo.getQuestions())) {
            //本次没选题目
            paper.setTotalScore(BigDecimal.ZERO);
            paper.setQuestionCount(0);
            save(paper);
            log.warn("本次{}组卷，没有选择题目！注意没有题目的试卷无法进行考试！！", paper);
            return paper;
        }
        // 2. 计算总分和题目数量
        // 计算总分
        BigDecimal totalScore
                = paperVo.getQuestions().values().stream()
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        // 设置总分
        paper.setTotalScore(totalScore);
        // 设置题目数量
        paper.setQuestionCount(paperVo.getQuestions().size());
        // 插入试卷记录到数据库insert和save的区别
        //baseMapper.insert(paper);
        save(paper);
        //对中间表进行操作，
        List<PaperQuestion> paperQuestions = paperVo.getQuestions().entrySet().stream()
                .map(entry -> new PaperQuestion(paper.getId().intValue(), entry.getKey().longValue(), entry.getValue()))
                .toList(); // 构建关联记录列表

        // 批量插入
        //方法一，使用foreach遍历插入
        //paperQuestions.forEach(paperQuestionMapper::insert);

        //方法二，批量导入不使用mapper,而是使用service，
        paperQuestionService.saveBatch(paperQuestions);
        return paper;
    }

    /**
     * AI智能组卷（新版）
     * @param aiPaperVo
     * @return
     */
    @Override
    public Paper makePaperByAi(AiPaperVo aiPaperVo) {
        //1. 试卷的基本属性赋值并保存 （名字 描述 时间 状态）
        Paper paper = new Paper();
        //copyUtils就相当于下面的情况，将属性名相同的内容，从前面的VO赋值到后面的实体类
        //paper.setName(paperVo.getName()); // 设置名称
        //paper.setDescription(paperVo.getDescription()); // 设置描述
        //paper.setDuration(paperVo.getDuration()); // 设置时长
        //paper.setStatus("DRAFT"); // 新建试卷默认为草稿状态
        BeanUtils.copyProperties(aiPaperVo, paper);
        //设置默认状态
        paper.setStatus("DRAFT");
        //存到数据库中，了解这个和BaseMapper.insert的区别
        save(paper);

        //2. 组卷规则下的试题选择和中间表的保存
        //先创建总分数和总数量的对象
        int questionCount = 0;
        BigDecimal totalScore = BigDecimal.ZERO;
        //遍历aiPaperVo.getRules()，里面存的是所有的问题要求
        for (RuleVo rule : aiPaperVo.getRules()) {
            //步骤1：校验规则下的题目数量 = 0 跳过
            //此时是该类型不需要出题
            if (rule.getCount() == 0) {
                log.warn("在：{}类型下，不需要出题！", rule.getType().name());
                continue;
            }
            //步骤2：查询当前规则下的所有题目集合 【type categoryIds】
            //设置查询条件
            LambdaQueryWrapper<Question> queryWrapper = new LambdaQueryWrapper<>();
            //相同类型
            queryWrapper.eq(Question::getType, rule.getType().name());
            //在类别不为空的条件下，获得对应类别ID下的题目
            queryWrapper.in(!ObjectUtils.isEmpty(rule.getCategoryIds()), Question::getCategoryId, rule.getCategoryIds());
            //根据条件进行select
            List<Question> allQuestionList = questionMapper.selectList(queryWrapper);

            //步骤3：校验查询的题目集合，集合为空！跳过本次！
            if (ObjectUtils.isEmpty(allQuestionList)) {
                log.warn("在：{}类型下，我们指定的分类：{},没有查询到题目信息！", rule.getType().name(), rule.getCategoryIds());
                continue;
            }

            //步骤4：判断下是否有规则下count数量！ 没有要全部了
            int realNumbers = Math.min(rule.getCount(), allQuestionList.size());

            //步骤5：本次规则下添加的数量和分数累加
            questionCount += realNumbers;
            totalScore = totalScore.add(BigDecimal.valueOf((long) realNumbers * rule.getScore()));

            //步骤6：先打乱数据，再截取需要题目数量
            Collections.shuffle(allQuestionList);
            List<Question> realQuestionList = allQuestionList.subList(0, realNumbers);

            //步骤7：转成中间表并进行保存
            List<PaperQuestion> paperQuestionList = realQuestionList.stream().map(question ->
                    new PaperQuestion(paper.getId().intValue(), question.getId(), BigDecimal.valueOf(rule.getScore()))
            ).collect(Collectors.toList());
            paperQuestionService.saveBatch(paperQuestionList);
        }
        //3. 修改试卷信息（总题数，总分数）
        paper.setQuestionCount(questionCount);
        paper.setTotalScore(totalScore);
        updateById(paper);
        //4. 返回试卷对象
        return paper;
    }

    /**
     * 更新试卷
     * @param id
     * @param paperVo
     * @return
     */
    @Override
    public Paper customUpdatePaper(Integer id, PaperVo paperVo) {
        //1.校验
        //两种情况不能修改试卷（1、发布状态 2、不同id,name相同）
        //根据ID获取试卷，为了获取试卷状态
        Paper paper = getById(id);
        //如果为发布状态，就无法删除
        if ("PUBLISHED".equals(paper.getStatus())){
            throw new RuntimeException("发布状态下的试卷不允许修改！");
        }
        //校验id name，如果重名，无法删除
        LambdaQueryWrapper<Paper> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.ne(Paper::getId, id);
        queryWrapper.eq(Paper::getName,paperVo.getName());
        //计算满足条件的个数
        long count = count(queryWrapper);
        //>0说明存在重名，无法删除
        if (count > 0){
            throw new RuntimeException("%s 试卷名字已经存在，请重新修改！".formatted(paperVo.getName()));
        }

        //2.试卷的主体
        BeanUtils.copyProperties(paperVo,paper);
        /*
           状态默认值： DRAFT
           总题目数： question长度
           总分数： question分数的和
         */
        //设置总数量
        paper.setQuestionCount(paperVo.getQuestions().size());
        //设置总分数
        paper.setTotalScore(paperVo.getQuestions().values().stream().reduce(BigDecimal.ZERO, BigDecimal::add));
        //更新paper
        updateById(paper);

        //3. 中间表的批量插入，实际是先删除，再进行批量插入
        //根据ID进行删除
        paperQuestionService.remove(new LambdaQueryWrapper<PaperQuestion>().eq(PaperQuestion::getPaperId,paper.getId()));
        //重新对中间表进行编辑，对中间表的对应列进行赋值，包括试卷ID：paperId,题目ID：questionId,每个题的真实成绩：score
        List<PaperQuestion> paperQuestionList = paperVo.getQuestions().entrySet().stream().
                map(entry -> new PaperQuestion(paper.getId().intValue(), Long.valueOf(entry.getKey()), entry.getValue()))
                .collect(Collectors.toList());

        //4. 中间表的批量插入
        paperQuestionService.saveBatch(paperQuestionList);
        return paper;
    }

    /**
     * 更新试卷状态（发布/停止）
     * @param id
     * @param status
     */
    @Override
    public void customUpdatePaperStatus(Integer id, String status) {
        //1、先进行校验，当目标状态为发布，且没有题目的时候，无法发布
        //状态为发布
        if ("PUBLISHED".equals(status)){
            LambdaQueryWrapper<PaperQuestion> queryWrapper = new LambdaQueryWrapper<>();
            //获取ID
            queryWrapper.eq(PaperQuestion::getPaperId,id);
            //通过中间表计算是否有题目的试卷ID是传入id
            long count = paperQuestionService.count(queryWrapper);
            //如果没有，无法发布
            if (count == 0){
                throw new RuntimeException("状态修改失败！目标为发布状态试卷必须有题目！");
            }
            //2.正常修改状态即可
            LambdaUpdateWrapper<Paper> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.set(Paper::getStatus,status);
            updateWrapper.eq(Paper::getId,id);
            update(updateWrapper);
        }
    }

    /**
     * 删除试卷
     * @param id
     */
    @Override
    public void customRemoveId(Integer id) {
        //1、先进行校验，当是发布状态无法删除，当考试记录中有记录也无法删除
        //1.不是发布状态
        Paper paper = getById(id);
        //前面的判断是否为空是为了避免后面可能出现空指针异常
        if (paper == null || "PUBLISHED".equals(paper.getStatus())){
            throw new RuntimeException("发布状态的试卷不能删除！");
        }
        //2.不能有关联的考试记录
        LambdaQueryWrapper<ExamRecord> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //查询考试记录表中是否有和传入的试题ID相同，如果存在，就说明有关联
        lambdaQueryWrapper.eq(ExamRecord::getExamId,id);
        //这里是记录和传入的试题ID相同的考试记录条数
        Long count = examRecordMapper.selectCount(lambdaQueryWrapper);
        //如果存在关联的考试记录，无法删除
        if (count > 0){
            throw new RuntimeException("当前试卷：%s 下面有关联 %s条考试记录！无法直接删除！".formatted(id,count));
        }
        //正常删除即可，先删除题目表中的所有数据
        //这里转换成long类型，是因为mybatis-plus虽然传入的时候是Object类型，但是他的底层其实是仅支持Long类型的
        removeById(Long.valueOf(id));
        //删除中间表中的题目Id对应的所有数据
        paperQuestionService.remove(new LambdaQueryWrapper<PaperQuestion>().eq(PaperQuestion::getPaperId,id));
    }

    //给与类型赋值
    private int typeToInt(String type){
        switch (type){
            case "CHOICE": return 1;
            case "JUDGE": return 2;
            case "TEXT": return 3;
            default: return 4;
        }
    }
}




